﻿using System.Collections.Generic;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RaptorEngine.Debugs;
using RaptorEngine.Managers;
using RaptorEngine.Particles;
using RaptorEngine.Threads;
using System;

namespace RaptorEngine
{
    /// <summary>
    /// Singleton Game Engine class.
    /// Uses multithreading and runs the Update method on a separate thread
    /// </summary>
    public class GameEngine
    {
        private static GameEngine _instance;

        private AudioManager _audio;
        private InputManager _input;
        private NetworkManager _network;
        private PhysicsManager _physics;
        private RenderManager _render;
        private SceneManager _scene;
        private Debug _debug;

        private Thread _updateThread;

        private ChangeBuffer _updateBuffer;
        public ChangeBuffer UpdateBuffer { get { return _updateBuffer; } set { _updateBuffer = value; } }

        public GraphicsDevice GraphicsDevice { get { return _game.GraphicsDevice; } }

        private DoubleBuffer _changeBuffers;
        internal DoubleBuffer ChangeBuffers
        {
            get { return _changeBuffers; }
        }

        public AudioManager Audio { get { return _audio; } }
        public InputManager Input { get { return _input; } }
        public NetworkManager Network { get { return _network; } }
        public PhysicsManager Physics { get { return _physics; } }
        public RenderManager Render { get { return _render; } }
        public SceneManager Scene { get { return _scene; } }
        public Debug Debug { get { return _debug; } }
        public Random Random { get; private set; }

        private int _nextId;

        public Game _game;
        private Worker _worker;

        /// <summary>
        /// Used to access the Game Engine
        /// </summary>
        public static GameEngine Instance
        {
            get { return _instance ?? (_instance = new GameEngine()); }
        }

        /// <summary>
        /// Call this method in your Game-class' Initialize-method
        /// </summary>
        /// <param name="game">Your Game-class</param>
        public void Initialize(Game game)
        {
            _game = game;
            _audio = new AudioManager();
            _input = new InputManager();
            _network = new NetworkManager();
            _physics = new PhysicsManager();
            _render = new RenderManager(game.GraphicsDevice);
            _scene = new SceneManager();

            Random = new Random();

            //Multithreading
            _changeBuffers = new DoubleBuffer(out _updateBuffer);
            _worker = new Worker();
            _updateThread = new Thread(_worker.ThreadUpdate);
            _updateThread.Start();

            _debug = new Debug();
        }

        /// <summary>
        /// Call this method in your Game-class' Initialize-method if you wish to use particle systems
        /// </summary>
        /// <param name="game">Your Game-class</param>
        /// <param name="particleSystems">The particle systems to use</param>
        public void Initialize(Game game, Dictionary<string, ParticleSystem> particleSystems)
        {
            Initialize(game);
            _render.Initialize(particleSystems);
        }

        /// <summary>
        /// Call this method in your Game-class' LoadContent-method
        /// </summary>
        public void LoadContent()
        {
            _scene.LoadContent();
            _render.LoadContent();
            _debug.LoadContent(_game.Content.Load<SpriteFont>("Fonts/DebugFont"));
            //Multithreading
            _worker.ResumeWork();
        }

        /// <summary>
        /// This method can be used just like the Game-class' Content.Load-method
        /// </summary>
        public T Load<T>(string name)
        {
            return _game.Content.Load<T>(name);
        }

        /// <summary>
        /// Call this method in your Game-class' Draw-method
        /// </summary>
        public void Draw(GameTime gameTime)
        {
            DoubleBuffer changeBuffers = Instance._changeBuffers;
            changeBuffers.GlobalStartFrame(gameTime);
            _render.Draw(gameTime);
        }

        internal int GetNextId()
        {
            return _nextId++;
        }

        /// <summary>
        /// Adds a new scene at the top of the scene-stack
        /// </summary>
        public void AddScene(Scene startScene)
        {
            _scene.PushScene(startScene);
        }

        /// <summary>
        /// Closes the Update-thread and exits the game.
        /// </summary>
        public void Exit()
        {
            _worker.RequestStop();
            _game.Exit();
        }
    }

    class Worker
    {
        private bool _stopThread;
        private bool _doWork;

        public void RequestStop()
        {
            _stopThread = true;
        }
        public void PauseWork()
        {
            _doWork = false;
        }
        public void ResumeWork()
        {
            _doWork = true;
        }

        public void ThreadUpdate()
        {
#if XBOX
            Thread.CurrentThread.SetProcessorAffinity(3);
#endif
            while (!_stopThread)
            {
                if (_doWork)
                {
                    GameTime gameTime;
                    GameEngine.Instance.UpdateBuffer = GameEngine.Instance.ChangeBuffers.StartUpdateProcessing(GameEngine.Instance.UpdateBuffer, out gameTime);
                    GameEngine.Instance.Scene.Update(gameTime);
                    GameEngine.Instance.Debug.Update(gameTime);
                    GameEngine.Instance.ChangeBuffers.SubmitUpdate();
                }
            }
        }
    }
}